﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using MDSCommon;
using System.Reflection;

namespace MDSAdministration
{
    /// <summary>
    /// Interaction logic for Deployment.xaml
    /// </summary>
    public partial class DeploymentWindow : Window
    {
        public DeploymentBuilder db { get; set; }
        private BackgroundWorker bw;

        List<string> MasterServers = new List<string>();
        List<string> SlaveServers = new List<string>();
        private int StepOrder;
        private string DeploymentFolder;

        List<DeploymentStep> lDeplymentSteps { get; set; }
        public DeploymentWindow()
        {
            InitializeComponent();
            
        }

        public bool Deployed { get; set; }
        public string DatabaseName { get; set; }
        private void btnDeploy_Click(object sender, RoutedEventArgs e)
        {
            DatabaseName = tbkDatabase.Text;
            
            if (!CheckDatabaseStatus())
            {
                Close();
                return;
            }

            bw = new BackgroundWorker();
            bw.WorkerReportsProgress = true;
            bw.WorkerSupportsCancellation = true;


            string logFolder = System.IO.Path.Combine(Environment.CurrentDirectory, "DeploymentLog");
            if (!Directory.Exists(logFolder))
            {
                Directory.CreateDirectory(logFolder);
            }

            SaveScripts(logFolder);

            bw.DoWork += new DoWorkEventHandler(bw_DoWork);
            bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
            bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

            StepOrder = 0;
            using (new WaitCursor())
            {
                btnDeploy.IsEnabled = false;
                tbiDeploy.IsSelected = false;
                tbiDeploy.IsEnabled = false;
                tbiDeployProg.IsSelected = true;
                tbiDeployProg.IsEnabled = true;
                btnOk.IsEnabled = false;
                btnResult.IsEnabled = false;
                btnCancel.IsEnabled = true;


                bw.RunWorkerAsync();
            }
        }

        private void SaveScripts(string logFolder)
        {
            StepOrder = 0;
            //Create folders
            DeploymentFolder = System.IO.Path.Combine(logFolder, DateTime.Now.ToString("yyyymmdd_hhmmss"));

            if (!Directory.Exists(DeploymentFolder))
            {
                Directory.CreateDirectory(DeploymentFolder);
            }

            string combinedFolder = System.IO.Path.Combine(DeploymentFolder, "Combined");
            if (!Directory.Exists(combinedFolder))
            {
                Directory.CreateDirectory(combinedFolder);
            }

            string combinedFile = System.IO.Path.Combine(combinedFolder, "Combined.sql");

            if (lDeplymentSteps.Count > 0)
            {
                using (StreamWriter wc = new StreamWriter(combinedFile, true))
                {
                    GeneratedHeaderMessage(wc);
                }
            }

            foreach (var dStep in lDeplymentSteps)
            {
                if (dStep.IsMaster)
                {
                    SaveQueryToFile(combinedFile, MasterServers, dStep);
                }
                else
                {
                    SaveQueryToFile(combinedFile, SlaveServers, dStep);
                }
            }
        }

        private void SaveQueryToFile(string combinedFile,  List<string> Servers, DeploymentStep dStep)
        {
            if (!String.IsNullOrEmpty(dStep.Query.ToString()))
            {
                foreach (var server in Servers)
                {
                    StepOrder++;
                    string filename = StepOrder.ToString().PadLeft(3,'0') + "_" + server.Replace("\\",".") + "_" + DatabaseName + "_" + dStep.StepName + ".sql";
                    
                    using (StreamWriter w = new StreamWriter(System.IO.Path.Combine(DeploymentFolder, filename)))
                    {
                        GeneratedHeaderMessage(w);
                        w.WriteLine("PRINT '--Step " + dStep.StepName + " started: " + StepOrder.ToString().PadLeft(3, '0') + "'");
                        WriteToFile(dStep.Query.ToString(), w, server);
                        w.WriteLine("PRINT '--Step " + dStep.StepName + "  Completed: " + StepOrder.ToString().PadLeft(3, '0') + "'");
                    }

                    using (StreamWriter wc = new StreamWriter(combinedFile, true))
                    {
                        wc.WriteLine("PRINT '--Step " + dStep.StepName + "  started: " + StepOrder.ToString().PadLeft(3, '0') + "'");
                        WriteToFile(dStep.Query.ToString(), wc, server);
                        wc.WriteLine("PRINT '--Step " + dStep.StepName + "  completed: " + StepOrder.ToString().PadLeft(3, '0') + "'");
                    }
                }
            }

        }

        private void GeneratedHeaderMessage(StreamWriter w)
        {
            w.WriteLine("-- This file was generated by MDSAdministration version: " + Assembly.GetExecutingAssembly().GetName().Version + " on: " + DateTime.Now );
        }

        private void WriteToFile(string query, StreamWriter w, string server)
        {
            w.WriteLine("GO");
            w.WriteLine("-- Run in SQLCMD mode");
            w.WriteLine(":CONNECT " + server);
            w.WriteLine("USE " + DatabaseName);
            w.WriteLine("GO");
            w.WriteLine("");
            w.Write(query);
        }

        private bool CheckDatabaseStatus()
        {
            List<string> servers = new List<string>();
            foreach (var step in lDeplymentSteps)
            {
                foreach (var server in step.Servers)
                {
                    if (servers.Contains(server) == false)
                    {
                        servers.Add(server);
                    }
                }
            }

            string errorMessage = "";
            DataSet ds = null;
            try
            {
                foreach (var s in servers)
                {
                    
                    DataSet dsMDSDatabases = DataHelper.GetDatabases(s);
                    if (dsMDSDatabases != null && dsMDSDatabases.Tables.Count > 0)
                    {
                        var result = from r in dsMDSDatabases.Tables[0].AsEnumerable()
                                     where r.Field<string>(Constants.DATABASE_NAME) == DatabaseName
                                     select r;
                        //dont check if it is not an mds databases
                        if (result.Count() == 0)
                        {
                            continue;
                        }
                    }

                    ds = DataHelper.CheckServiceBroker(s, DatabaseName);
                    if (ds != null && ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
                    {
                        if (((bool)ds.Tables[0].Rows[0][Constants.DATABASE_CHECK_IS_BROKER_ENABLED]) == false)
                        {
                            errorMessage += "Server: " + s + " Service Broker is not enabled." + Environment.NewLine;
                        }
                    }
                    else
                    {
                        errorMessage += "Server: " + s + " error in retrieving Service Broker Details" + Environment.NewLine;
                        break;
                    }

                    ds = DataHelper.CheckCLR(s, DatabaseName);
                    if (ds != null && ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
                    {
                        if (((bool)ds.Tables[0].Rows[0][Constants.DATABASE_CHECK_IS_CLR_ENABLED]) == false)
                        {
                            errorMessage += "Server: " + s + " CLR is not enabled." + Environment.NewLine;
                        }
                    }
                    else
                    {
                        errorMessage += "Server: " + s + " error in retrieving CLR Details" + Environment.NewLine;
                        break;
                    }

                }
            }
            catch (Exception ex)
            {
                errorMessage += "Check Database Status error." + Environment.NewLine;
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.InnerException);
                Console.WriteLine(ex.StackTrace);
            }
            

            if (errorMessage.Length > 0)
            {
                MessageBox.Show("The database appears to have been restored and the following issues need to be resolved:" + Environment.NewLine + errorMessage, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return false;

            }

            return true;
        }

        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bWorker = sender as BackgroundWorker;

            foreach (var dStep in lDeplymentSteps)
            {
                if ((bWorker.CancellationPending == true))
                {
                    e.Cancel = true;
                    break;
                }

                if (dStep.IsMaster)
                {
                    runDeploySql(MasterServers, dStep);
                }
                else
                {
                    runDeploySql(SlaveServers, dStep);
                }
            }
        }

        private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            using (StreamWriter w = new StreamWriter(System.IO.Path.Combine(DeploymentFolder, "Executio_Log.txt"), true))
            {
                w.Write(e.UserState.ToString());
            }

            txtResult.Text += e.UserState.ToString();
            txtResult.Focus();
            txtResult.CaretIndex = txtResult.Text.Length;
        }

        //public Exception Error;
        private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            tbiDeploy.IsEnabled = true;
            Deployed = true;
            btnOk.IsEnabled = true;
            btnResult.IsEnabled = true;
            btnCancel.IsEnabled = false;
        }

        private void runDeploySql(List<string> Servers, DeploymentStep dStep)
        {
            if (!String.IsNullOrEmpty(dStep.Query.ToString()))
            {
                foreach (var server in Servers)
                {
                    StepOrder++;

                    deploySql(server, DatabaseName, "PRINT '--Step  " + dStep.StepName + " started: " + StepOrder.ToString().PadLeft(3, '0') + "'");

                    deploySql(server, DatabaseName, dStep.Query.ToString());

                    deploySql(server, DatabaseName, "PRINT '--Step  " + dStep.StepName + " completed: " + StepOrder.ToString().PadLeft(3, '0') + "'");
                }
            }
        }
        private void deploySql(string serverName, string databaseName, string sqlQuery)
        {
            var eventHandler = new SqlInfoMessageEventHandler(DataHelper_SqlConnOnInfoMessage);
            DataHelper.SqlConnOnInfoMessage += eventHandler;

            string runQuery = "";
            try
            {
                foreach (string query in sqlQuery.Split(new string[] {"GO" + Environment.NewLine}, StringSplitOptions.None))
                {
                    bw.ReportProgress(0, Environment.NewLine + "---Running code on: " + serverName + Environment.NewLine);
                    
                    if (!String.IsNullOrEmpty(query.Trim()))
                    {
                        runQuery = query;
                        DataHelper.GetData(query, serverName, databaseName);
                    }
                }
            }
            catch(System.Exception ex)
            {
                string message = "";
                message += Environment.NewLine + "Error in Deployment" + Environment.NewLine;
                message += Environment.NewLine + "---Error on Server: " + serverName;
                message += Environment.NewLine + "Error Query " + Environment.NewLine;
                message += Environment.NewLine + runQuery + Environment.NewLine;
                message += "Error Message" + Environment.NewLine;
                message += ex.Message + Environment.NewLine;
                message += Environment.NewLine + "Error Inner Exception" + Environment.NewLine;
                message += ex.InnerException + Environment.NewLine;
                message += Environment.NewLine + "Error Stack Trace" + Environment.NewLine;
                message += ex.StackTrace + Environment.NewLine;

                bw.ReportProgress(0, message);
            }
            DataHelper.SqlConnOnInfoMessage -= eventHandler;
        }

        void DataHelper_SqlConnOnInfoMessage(object sender, SqlInfoMessageEventArgs e)
        {
            bw.ReportProgress(0, e.Message + Environment.NewLine);
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {   
            lblServerName.Content = db.Server.ServerName;
            tbkDatabase.Text = db.Server.Database;
            lblModel.Content = db.Server.Model;
            lblModelVersion.Content = db.Server.ModelVersion;

            txtCurrentUser.Text = db.CurrentUser;
            try
            {
                DataTable dt = DataHelper.CheckIfReplication(db.Server.ServerName, db.Server.Database);
                if (dt != null && dt.Rows.Count > 0)
                {
                    db.IsReplicated = true;
                    
                    SlaveServers.Clear();
                    foreach (DataRow dr in dt.Rows)
                    {
                        SlaveServers.Add(dr[Constants.REPLICATION_SUBSCRIBER].ToString());

                        if (String.IsNullOrEmpty(db.PublicationName))
                        {
                            db.PublicationName = dr[Constants.REPLICATION_PUBLICATION].ToString();
                        }
                        else
                        {
                            if (db.PublicationName != dr[Constants.REPLICATION_PUBLICATION].ToString())
                            {
                                //making an assumption that they are named the same on all servers
                                throw new Exception("Replication Publication Name is different between servers.");
                            }
                        }
                    }

                }
                else
                {
                    db.IsReplicated = false;
                }

                //Is always one server
                MasterServers.Clear();
                MasterServers.Add(db.Server.ServerName);

                BuildDeploymentSteps();

                lbxLoadSteps.ItemsSource = lDeplymentSteps;
                lbxLoadSteps.DisplayMemberPath = "StepName";

                if (lbxLoadSteps.Items.Count > 0)
                {
                    lbxLoadSteps.SelectedIndex = 0;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                this.Close();
            }
            
        }

        private void BuildDeploymentSteps()
        {
            var queries = db.Build();

            lDeplymentSteps = new List<DeploymentStep>();

            if (db.IsReplicated)
            {
                foreach (var step in Enum.GetValues(typeof (DeploymentBuildOrder)).Cast<DeploymentBuildOrder>())
                {
                    if (String.IsNullOrEmpty(queries[step].Query.ToString()) == false)
                    {
                        queries[step].StepName = step.ToString();

                        if (queries[step].IsMaster)
                        {
                            queries[step].Servers = MasterServers;

                        }
                        else
                        {
                            queries[step].Servers = SlaveServers;
                        }
                        lDeplymentSteps.Add(queries[step]);
                    }
                }
            }
            else
            {
                var masterStep = new DeploymentStep();
                masterStep.IsMaster = true;
                masterStep.StepName = "Master";
                masterStep.Servers = MasterServers;

                foreach (var step in Enum.GetValues(typeof (DeploymentBuildOrder)).Cast<DeploymentBuildOrder>())
                {
                    if (queries[step].IsMaster && String.IsNullOrEmpty(queries[step].Query.ToString()) == false )
                    {
                        masterStep.Query.Append(queries[step].Query.ToString());
                        masterStep.Query.Append(Environment.NewLine);
                    }
                }
                lDeplymentSteps.Add(masterStep);
            }
        }

        private void btnOk_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }

        private void lbxLoadSteps_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems != null && e.AddedItems.Count > 0 && e.AddedItems[0].GetType().FullName == Constants.MDSADMINISTRATION_DEPLOYMENTSTEP)
            {
                var ds = (DeploymentStep)e.AddedItems[0];
                tbLoadQuery.Text = ds.Query.ToString();
                lbxLoadServers.ItemsSource = ds.Servers;
            }
        }

        private void btnSave_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                DatabaseName = tbkDatabase.Text;

                var fbd = new System.Windows.Forms.FolderBrowserDialog();
                if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    SaveScripts(fbd.SelectedPath);
                }

                Process.Start("explorer.exe", DeploymentFolder);
            }
            catch (Exception)
            {
                MessageBox.Show("Error in savign file/s","Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private void btnResult_Click(object sender, RoutedEventArgs e)
        {
            Process.Start("explorer.exe", DeploymentFolder);
        }

        private void Window_Closing(object sender, CancelEventArgs e)
        {
            if (bw != null && bw.WorkerSupportsCancellation == true && bw.IsBusy)
            {
                e.Cancel = true;
                MessageBox.Show("A deployment is still in progress. First press Cancel before exiting.", "Warning",
                    MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        private void CancelWorker()
        {
            if (bw != null && bw.WorkerSupportsCancellation == true && bw.IsBusy)
            {
                DataHelper.command.Cancel();
                bw.CancelAsync();
            }
        }

        private void btnCancel_Click(object sender, RoutedEventArgs e)
        {
            CancelWorker();
        }
    }

    public class DeploymentStep
    {
        public List<string> Servers { get; set; }
        public Boolean IsMaster { get; set; }
        public StringBuilder Query { get; set; }

        public string StepName { get; set; }

        public DeploymentStep()
        {
            Query = new StringBuilder();
        }
    }
}
